package sv;

import java.util.ArrayList;

import javax.swing.JOptionPane;

public class SolverRule {
	private static ToolBox tb;
	private OutputWindow oWindow;
	private int nS = 2;
	private boolean[][] choose = new boolean[9][9];
	static GraphicsWhindow gWindow;
	private boolean risolto = false;
	private boolean irr = false;
	private boolean show=true;

	public SolverRule(int s, boolean test, boolean show) { // costruttore Solver
		this.nS = nS;
		this.show=show;
		tb = new ToolBox();
		if (show) {
			oWindow = new OutputWindow("Solver3", 2);
			if (test)
				oWindow.setPosition(341*2+3, 23);
			else
				oWindow.setLocationRelativeTo(null);
		}
		boolean[][][] matrix = tb.getMatrix(nS);
		if(show)
			mostarInFinestra(matrix, oWindow);
		Solver so = new Solver(nS);

		while (!tb.isOver(nS) && !irr) {
			risolvi(matrix, s, 0, 0);
		}

		if (!irr) {
			System.out.println(nS + "--" + s + " Sudoku risolto");
			if (!test)
				JOptionPane.showMessageDialog(null, "Sudoku risolto");
		} else {
			System.out.println(nS + "--" + s + " Sudoku non risolvibile");
			if (!test)
				JOptionPane.showMessageDialog(null, "Sudoku non risolvibile");
		}

		int rit = ToolBox.getRit();
		Object wait = ToolBox.getWait();
		if (!tb.isForce() && rit > 0) {
			try {
				synchronized (wait) {
					wait.wait(rit);
				}
			} catch (java.lang.InterruptedException ie) {
			}
		}

		if(show)
			oWindow.dispose();

	}

	private void risolvi(boolean[][][] matrix, int s, int r, int c) {
		while (!tb.isOver(nS) && !irr) {
			boolean endA = true;
			int endB = 1;
			int endC = 1;

			// System.out.println("Applico regola A - ");
			endA = ruleA(matrix, 2);
			if(show)
				mostarInFinestra(matrix, oWindow);

			int rit = ToolBox.getRit();
			Object wait = ToolBox.getWait();
			if (!tb.isOver(nS) && !endA && rit > 0) {
				try {
					synchronized (wait) {
						wait.wait(rit);
					}
				} catch (java.lang.InterruptedException ie) {
				}
			}

			// System.out.println("Applico regola B - ");
			endB = ruleB(matrix, 2);
			if(show)
				mostarInFinestra(matrix, oWindow);

			rit = ToolBox.getRit();
			if (!tb.isOver(nS) && endB==0 && rit > 0) {
				try {
					synchronized (wait) {
						wait.wait(rit);
					}
				} catch (java.lang.InterruptedException ie) {
				}
			}

			if (endB == -1) {
				irr = true;
				// System.out.println(nS + "--" + s +
				// " Sudoku non risolvibile per regola B");
			}

			// System.out.println(endA + " " + endB);

			if (endA && endB == 1) {
				// System.out.println("Applico regola C - ");
				endC = ruleC(matrix, 2);
				if(show)
					mostarInFinestra(matrix, oWindow);

				rit = ToolBox.getRit();
				if (!tb.isOver(nS) && endC==0 && rit > 0) {
					try {
						synchronized (wait) {
							wait.wait(rit);
						}
					} catch (java.lang.InterruptedException ie) {
					}
				}
			}

			if (endC == -1) {
				irr = true;
				// System.out.println(nS + "--" + s+
				// " Sudoku non risolvibile per regola C");
			}

			if (endA && endB == 1 && endC == 1) {
				if (risolto(matrix))
					tb.setOver(nS);
				else {
					// System.out.println("Provo");
					int[] index = null;
					index = Solver.getIndex(matrix, nS, choose);

					// System.out.println(index[0] + " " + index[1]);
					if (index[0] != -1) {
						Solver.setChoose(true, index[0], index[1], choose);
						// dato che ho ricevuto questo indice sono sicuro di
						// avere piu'
						// valori
						int[] values = Solver.possibleValues(
								matrix[index[0]][index[1]], nS);
						for (int i = 0; i < values.length; i++) {
							tb.addPassi(nS);
							// crea un nodo per ogni possibile figlio
							boolean[] set = new boolean[] { false, false,
									false, false, false, false, false, false,
									false };
							set[values[i] - 1] = true;
							boolean[][][] newMatrix = Solver.copyMatrix(matrix);// copio
																				// la
							// tabella

							newMatrix[index[0]][index[1]] = set; // e assegno il
																	// nuovo
																	// valore
							if (irr) {
								irr = false;
							}
							risolvi(newMatrix, s, index[0], index[1]);
						}
						Solver.setChoose(false, index[0], index[1], choose);
					} else {
						tb.setOver(nS);
					}
				}
			}
		}
	}

	private boolean risolto(boolean[][][] matrix) {
		int ok = 0;
		for (int r = 0; r < 9; r++) {
			for (int c = 0; c < 9; c++) {
				tb.addPassi(nS);
				if (Solver.hasOneTrue(matrix, r, c, 2)) {
					ok++;
				}

			}
		}
		if (ok == 81)
			return true;
		return false;
	}

	/**
	 * Metodo che si occupa della visualizzazione del cubo con i possibili
	 * valori
	 * 
	 * @param matrix
	 *            matrice da mostrare
	 * @param oWindow
	 *            finestra in cui mostrare la matrice matrix
	 */
	public static void mostarInFinestra(boolean[][][] matrix,
			OutputWindow oWindow) {
		for (int r = 0; r < 9; r++)
			for (int c = 0; c < 9; c++) {
				ArrayList<Integer> val = new ArrayList();
				for (int k = 0; k < 9; k++) {
					if (matrix[r][c][k])
						val.add(k + 1);
				}
				oWindow.putElement(r, c, val);
			}
	}

	/**
	 * Controlla per ogni cella se nelle righe e nelle colonne correlate resta
	 * disponibile solo un valore, ed in tal caso lo sostituisce nella matrice.
	 * 
	 * @param matrix
	 */
	private boolean ruleA(boolean[][][] matrix, int nS) {
		boolean end = true;
		// controllo su riga
		// System.out.println("\tcontrollo righe");
		for (int c = 0; c < 9; c++) {
			for (int r = 0; r < 9; r++) {
				tb.addPassi(nS);
				if (!Solver.hasOneTrue(matrix, r, c, 2)) {
					boolean[] availableValues = matrix[r][c].clone();
					// controllo gli altri elementi della riga
					for (int i = 0; i < 9; i++) {
						tb.addPassi(nS);
						if (i != c) {
							for (int j = 0; j < 9; j++) {
								tb.addPassi(nS);
								availableValues[j] = availableValues[j]
										&& (!matrix[r][i][j]);
							}
						}
					}
					// controlla se e' rimasto solo un valore
					// disponibile
					int count = 0;
					for (int j = 0; j < 9; j++) {
						tb.addPassi(nS);
						if (availableValues[j])
							count++;
					}
					if (count == 1) {
						// il valore e' univoco, lo sostituiamo nella
						// matrice
						matrix[r][c] = availableValues;
						end = false;
					}
				}
			}
		}

		// if (end) {
		// controllo su colonna
		// System.out.println("\tcontrollo colonne");
		for (int c = 0; c < 9; c++) {
			for (int r = 0; r < 9; r++) {
				tb.addPassi(nS);
				if (!Solver.hasOneTrue(matrix, r, c, 2)) {
					boolean[] availableValues = matrix[r][c].clone();
					// controllo gli altri elementi della colonna
					for (int i = 0; i < 9; i++) {
						tb.addPassi(nS);
						if (i != r) {
							for (int j = 0; j < 9; j++) {
								tb.addPassi(nS);
								availableValues[j] = availableValues[j]
										&& (!matrix[i][c][j]);
							}
						}
					}
					// controlla se e' rimasto solo un valore
					// disponibile
					int count = 0;
					for (int j = 0; j < 9; j++) {
						tb.addPassi(nS);
						if (availableValues[j])
							count++;
					}
					if (count == 1) {
						// il valore e' univoco, lo sostituiamo
						// nella
						// matrice
						matrix[r][c] = availableValues;
						end = false;
					}
				}
			}
		}

		// if (end) {
		// System.out.println("\tcontrollo quadrato");
		// controllo su quadrato
		for (int c = 0; c < 9; c++) {
			for (int r = 0; r < 9; r++) {
				tb.addPassi(nS);
				if (!Solver.hasOneTrue(matrix, r, c, 2)) {
					// controllo gli altri elementi del quadrato
					boolean[] availableValues = checkSquare(matrix, r, c);
					// controlla quanti valori sono presenti
					int count = 0;
					for (int i = 0; i < 9; i++) {
						tb.addPassi(nS);
						if (availableValues[i])
							count++;
					}
					if (count == 1) {
						// solo un valore disponibile, va sostituito
						matrix[r][c] = availableValues;
						end = false;
					}
				}
			}
		}
		// }
		// }
		return end;
	}

	private boolean[] checkSquare(boolean[][][] matrix, int r, int c) {
		// trova gli indici di partenza del quadrato
		int startRow = (r / 3) * 3;
		int startCol = (c / 3) * 3;
		boolean[] availableValues = matrix[r][c].clone();
		for (int ri = startRow; ri < startRow + 3; ri++) {
			for (int co = startCol; co < startCol + 3; co++) {
				if (!(ri == r && co == c)) {
					for (int i = 0; i < 9; i++) {
						tb.addPassi(nS);
						availableValues[i] = availableValues[i]
								&& !matrix[ri][co][i];
					}
				}
			}
		}

		return availableValues;
	}

	public int ruleB(boolean[][][] matrix, int nS) {
		// int[][] input = tb.getInput();
		int end = 1;
		for (int r = 0; r < 9; r++)
			for (int c = 0; c < 9; c++) {
				tb.addPassi(nS);
				if (Solver.hasOneTrue(matrix, r, c, nS)) {
					int[] val = Solver.possibleValues(matrix[r][c], nS);
					int tEnd = ruleB1(matrix, val[0], r, c, nS);
					if (tEnd == -1) {
						// System.out.println("Error on [" + r + "] [" + c +
						// "]");
						return -1;
					}
					if (end == 1)
						end = tEnd;
				}
			}
		return end;
	}

	/**
	 * Regola che impone l'univocita' del valore sulla stessa riga, colonna e
	 * all'interno dello stesso quadrato
	 * 
	 * @param cubo_valori
	 *            matrice con valori
	 * @param val
	 *            valore assegnato alla cella
	 * @param r
	 *            indice riga della cella
	 * @param c
	 *            indice colonna della cella
	 * @param nS
	 *            numero Solver
	 */
	private int ruleB1(boolean[][][] cubo_valori, int val, int r, int c, int nS) {
		int end = 1;
		// eliminazione valori dalla riga r
		for (int co = 0; co < 9; co++) {
			tb.addPassi(nS);
			if (co != c) {
				boolean hasOneTrue = Solver.hasOneTrue(cubo_valori, r, co, nS);
				if (cubo_valori[r][co][val - 1])
					end = 0;
				cubo_valori[r][co][val - 1] = false;
				int i = 0;
				while (i < 9 && !cubo_valori[r][co][i]) {
					tb.addPassi(nS);
					i++;
				}
				if (i == 9)
					return -1;
				if (!hasOneTrue && Solver.hasOneTrue(cubo_valori, r, co, nS)) {
					ruleB(cubo_valori, nS);
				}
			}
		}

		// eliminazione valori dalla colonna c
		for (int ri = 0; ri < 9; ri++) {
			tb.addPassi(nS);
			if (ri != r) {
				boolean hasOneTrue = Solver.hasOneTrue(cubo_valori, ri, c, nS);
				if (cubo_valori[ri][c][val - 1])
					end = 0;
				cubo_valori[ri][c][val - 1] = false;
				int j = 0;
				while (j < 9 && !cubo_valori[ri][c][j]) {
					tb.addPassi(nS);
					j++;
				}
				if (j == 9)
					return -1;
				if (!hasOneTrue && Solver.hasOneTrue(cubo_valori, ri, c, nS)) {
					ruleB(cubo_valori, nS);
				}
			}
		}

		// eliminazione valori dal quadrato
		int nr_quad = (r / 3) * 3;
		int nc_quad = (c / 3) * 3;
		for (int i = nr_quad; i < nr_quad + 3; i++) {
			for (int j = nc_quad; j < nc_quad + 3; j++) {
				tb.addPassi(nS);
				if (!(i == r && j == c)){
					boolean hasOneTrue = Solver.hasOneTrue(cubo_valori, i, j, nS);
					if (cubo_valori[i][j][val - 1])
						end = 0;
					cubo_valori[i][j][val - 1] = false;
					int k = 0;
					while (k < 9 && !cubo_valori[i][j][k]) {
						tb.addPassi(nS);
						k++;
					}
					if (k == 9)
						return -1;
					if (!hasOneTrue && Solver.hasOneTrue(cubo_valori, i, j, nS)) {
						ruleB(cubo_valori, nS);
					}
				}
			}

		}
		return end;
	}

	/**
	 * Toglie ai domini delle celle esterne al quadrato che contiene la cella i
	 * valori che sicuramente assumeranno le celle interne al quadrato
	 * 
	 * @param cubo_valori
	 *            matrice con dati sudoku
	 * @param r
	 *            indice riga
	 * @param c
	 *            indice colonna
	 */

	private int ruleC(boolean[][][] matrix, int nS) {
		int end = 1;

		for (int br = 0; br < 3; br++) {
			for (int bc = 0; bc < 3; bc++) {
				for (int v = 0; v < 9; v++) {
					tb.addPassi(nS);
					int[][] pos = new int[3][];
					int count = 0;
					for (int r = br * 3; r < br * 3 + 3; r++) {
						for (int c = bc * 3; c < bc * 3 + 3; c++) {
							tb.addPassi(nS);
							if (!Solver.hasOneTrue(matrix, r, c, nS)) {
								if (matrix[r][c][v]) {
									if (count < 3)
										pos[count] = new int[] { r, c };
									count++;
								}
							}
						}
					}
					if (count > 1 && count < 4) {
						boolean okR = true;
						boolean okC = true;

						for (int i = 0; i < count - 1; i++) {
							if (!(okR && pos[i][0] == pos[i + 1][0]))
								okR = false;
							if (!(okC && pos[i][1] == pos[i + 1][1]))
								okC = false;
						}

						if (okR) {
							for (int c = 0; c < 9; c++) {
								if (c < bc * 3 || c >= bc * 3 + 3) {
									boolean hasOneTrue = Solver.hasOneTrue(matrix, pos[0][0], c, nS);
									tb.addPassi(nS);
									if (matrix[pos[0][0]][c][v])
										end = 0;
									matrix[pos[0][0]][c][v] = false;
									int i = 0;
									while (i < 9 && !matrix[pos[0][0]][c][i]) {
										tb.addPassi(nS);
										i++;
									}
									if (i == 9)
										return -1;
									if (!hasOneTrue && Solver.hasOneTrue(matrix, pos[0][0], c, nS)) {
										ruleB(matrix, nS);
									}
								}
							}
						}

						if (okC) {
							for (int r = 0; r < 9; r++) {
								if (r < br * 3 || r >= br * 3 + 3) {
									boolean hasOneTrue = Solver.hasOneTrue(matrix, r, pos[0][1], nS);
									tb.addPassi(nS);
									if (matrix[r][pos[0][1]][v])
										end = 0;
									matrix[r][pos[0][1]][v] = false;
									int i = 0;
									while (i < 9 && !matrix[r][pos[0][1]][i]) {
										tb.addPassi(nS);
										i++;
									}
									if (i == 9)
										return -1;
									if (!hasOneTrue && Solver.hasOneTrue(matrix, r, pos[0][1], nS)) {
										ruleB(matrix, nS);
									}
								}
							}
						}
					}
				}
			}
		}

		return end;
	}
}